using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.FxCop.Sdk;
using Microsoft.BizTalk.XLANGs.BTXEngine;
using Microsoft.XLANGs.BaseTypes;
using Microsoft.BizTalk.PipelineOM;

namespace BizTalkCop.Rules.Bases
{
    /// <summary>
    /// Base class for FxCop rules running over BizTalk types
    /// </summary>
    /// <remarks>
    /// Exposes helper methods to determine BizTalk types and retrieve attributes
    /// </remarks>
    public abstract class RuleBase : BaseIntrospectionRule
    {
        #region Constructors

        /// <summary>
        /// Constructor with known state
        /// </summary>
        /// <param name="name">Rule name, key for Rules.xml</param>
        protected RuleBase(string name) : base(name, "BizTalkCop.Rules.Resources.Rules.xml", typeof(RuleBase).Assembly){ }

        #endregion

        #region Protected static methods

        /// <summary>
        /// Returns whether the given type is an orchestration
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is an orchestration</returns>
        protected static bool IsOrchestration(TypeNode type)
        {
            return IsChildOfType<BTXService>(type);
        }

        /// <summary>
        /// Returns whether the given type is a BizTalk map
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is a map</returns>
        protected static bool IsMap(TypeNode type)
        {
            return IsChildOfType<TransformBase>(type);
        }

        /// <summary>
        /// Returns whether the given type is a BizTalk document schemae
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is a document schema</returns>
        protected static bool IsDocumentSchema(TypeNode type)
        {
            return IsSchema(type, SchemaTypeEnum.Document);
        }

        /// <summary>
        /// Returns whether the given type is a BizTalk property schema
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is a property schema</returns>
        protected static bool IsPropertySchema(TypeNode type)
        {
            return IsSchema(type, SchemaTypeEnum.Property);
        }

        /// <summary>
        /// Returns whether the given type is a BizTalk receive pipeline
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is a receive pipeline</returns>
        protected static bool IsReceivePipeline(TypeNode type)
        {
            return IsChildOfType<ReceivePipeline>(type);
        }

        /// <summary>
        /// Returns whether the given type is a BizTalk send pipeline
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is a send pipeline</returns>
        protected static bool IsSendPipeline(TypeNode type)
        {
            return IsChildOfType<SendPipeline>(type);
        }

        /// <summary>
        /// Gets the value of the first argument of the given attribute on the member
        /// </summary>
        /// <typeparam name="TAttribute">Attribute type to fetch</typeparam>
        /// <param name="member">Member to fetch for</param>
        /// <returns>Attribute's first argument value or string.Empty if none</returns>
        protected static string GetAttributeArgument<TAttribute>(Member member)
        {
            return GetAttributeArgument<TAttribute>(member, 0);
        }

        /// <summary>
        /// Gets the value of the argument of the given attribute on the member
        /// </summary>
        /// <typeparam name="TAttribute">Attribute type to fetch</typeparam>
        /// <param name="member">Member to fetch for</param>
        /// <param name="argumentPosition">Position ot argument to fetch</param>
        /// <returns>Attribute's argument value or string.Empty if none</returns>
        protected static string GetAttributeArgument<TAttribute>(Member member, int argumentPosition)
        {
            string value = string.Empty;
            AttributeNode attribute = GetAttribute<TAttribute>(member);
            if (attribute != null)
            {
                value = (attribute.GetPositionalArgument(argumentPosition) as Literal).Value.ToString();
            }
            return value;
        }

        /// <summary>
        /// Gets the default value of the field name on the given type
        /// </summary>
        /// <param name="type">Type to fetch from</param>
        /// <param name="fieldName">Name of field to fetch</param>
        /// <returns>Field value or string.Empty if none</returns>
        protected static string GetFieldDefaultValue(TypeNode type, string fieldName)
        {
            string value = string.Empty;
            if (IsOrchestration(type))
            {
                Field field = null;
                foreach (Member member in type.Members)
                {
                    if (member is Field && member.Name.Name == fieldName)
                    {
                        field = (Field)member;
                        break;
                    }
                }
                if (field != null)
                {
                    value = field.DefaultValue.ToString();
                }
            }
            return value;
        }

        /// <summary>
        /// Returns whether the type is a direct child of the given type
        /// </summary>
        /// <typeparam name="T">Base type</typeparam>
        /// <param name="type">Type to check</param>
        /// <returns>Whether type is a child class of base type</returns>
        protected static bool IsChildOfType<T>(TypeNode type)
        {
            return IsChildOfType(type, typeof(T));
        }

        /// <summary>
        /// Returns whether the type is a direct child of the given type
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <param name="parentType">Type to check</param>
        /// <returns>Whether type is a child class of base type</returns>
        protected static bool IsChildOfType(TypeNode type, Type parentType)
        {
            return (type.BaseType.Name.Name == parentType.Name);
        }

        /// <summary>
        /// Returns whether the member is a field of the given type
        /// </summary>
        /// <typeparam name="T">Type to check for</typeparam>
        /// <param name="member">Member to check</param>
        /// <returns>Whether member is a field of the given type</returns>
        protected static bool IsFieldOfType<T>(Member member)
        {
            Field field = member as Field;
            bool isInstance = (field != null && !field.IsSpecialName);
            if (isInstance)
            {
                isInstance = field.Type.Name.Name == typeof(T).Name;
            }
            return (isInstance);
        }

        /// <summary>
        /// Gets the first attribute of the given type declared against the member
        /// </summary>
        /// <typeparam name="TAttribute">Attribute to fetch</typeparam>
        /// <param name="member">Member to fetch from</param>
        /// <returns>Attribute of type, or null if none</returns>
        protected static AttributeNode GetAttribute<TAttribute>(Member member)
        {
            return GetAttribute<TAttribute>(member, true);
        }

        /// <summary>
        /// Gets the first attribute of the given type declared against the member
        /// </summary>
        /// <typeparam name="TAttribute">Attribute to fetch</typeparam>
        /// <param name="member">Member to fetch from</param>
        /// <param name="checkParent">Whether to check immediate parent if attribute not found</param>
        /// <returns>Attribute of type, or null if none</returns>
        protected static AttributeNode GetAttribute<TAttribute>(Member member, bool checkParent)
        {
            AttributeNode node = null;
            foreach (AttributeNode attribute in member.Attributes)
            {
                if (attribute.Type.FullName == typeof(TAttribute).FullName)
                {
                    node = attribute;
                    break;
                }
            }
            if (node == null && checkParent && member.DeclaringType != null)
            {
                node = GetAttribute<TAttribute>(member.DeclaringType, false);
            }
            return node;
        }

        #endregion

        #region Private static methods

        private static bool IsSchema(TypeNode type, SchemaTypeEnum expectedSchemaType)
        {
            bool isSchemaType = IsChildOfType<SchemaBase>(type);
            if (isSchemaType)
            {
                AttributeNode schemaTypeAttribute = GetAttribute<SchemaTypeAttribute>(type);
                //schema type is held as a literal:
                SchemaTypeEnum schemaType = (SchemaTypeEnum)(schemaTypeAttribute.GetPositionalArgument(0) as Literal).Value;
                isSchemaType = (schemaType == expectedSchemaType);
            }
            return isSchemaType;
        }

        #endregion
    }
}
